package game;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.swing.event.EventListenerList;

import listeners.LevelListener;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;

import entities.Entity;
import entities.Player;

public final class Level {

	private final String name;
	private final Map<Class<? extends Entity>, Properties> entityMap;

	private EntitySpawnManager spawnManager;

	private List<Entity> entities;

	private List<Entity> spawnList;
	private List<Entity> killList;

	private Player player;

	private EventListenerList listeners;

	public Level(HashMap<Class<? extends Entity>, Properties> entityMap,
			String name) {
		this.name = name;
		this.entityMap = entityMap;
		reset();
	}

	/**
	 * Should only be invoked via Entity.spawn()!
	 * 
	 * @param entity
	 */
	public void addEntity(Entity entity) {
		spawnList.add(entity);
	}

	/**
	 * Should only be invoked via Entity.unspawn()!
	 * 
	 * @param e
	 */
	public void removeEntity(Entity e) {
		killList.add(e);
	}

	public void tick(float dt) {
		for (Entity e : spawnList) {
			entities.add(e);
		}
		for (Entity e : killList) {
			entities.remove(e);
		}
		killList.clear();
		spawnList.clear();

		for (Entity e : entities) {
			e.tick(dt);
		}

	}

	public void render(GameContainer gc, Graphics g) {
	}

	public Player getPlayer() {
		return player;
	}

	/**
	 * Calculates and returns all collisions of an entity
	 * 
	 * @param e
	 *            the tested entity
	 * @return
	 */
	public List<Collision<Entity>> getCollisions(Entity e) {
		ArrayList<Collision<Entity>> collisions = new ArrayList<Collision<Entity>>();
		for (Entity other : entities) {
			if (e != other && e.collidesWith(other)) {
				collisions.add(new Collision<Entity>(e, other, true));
			}
		}
		return collisions;
	}

	/**
	 * Calculates and returns collisions of an entity with all entities of a
	 * given class.
	 * 
	 * @param e
	 *            the tested entity
	 * @param c
	 *            the class of the tested entities. If null, the result of<br>
	 *            {@link #getCollisions(Entity)} will be returned
	 * @return a {@link Collision}
	 */
	public List<Collision<Entity>> getCollisions(Entity e,
			Class<? extends Entity> c) {
		if (c == null) {
			return getCollisions(e);
		}
		ArrayList<Collision<Entity>> collisions = new ArrayList<Collision<Entity>>();
		for (Entity other : entities) {
			if (e != other && c.isAssignableFrom(e.getClass())
					&& e.collidesWith(other)) {
				Collision<Entity> collision = new Collision<Entity>(e, other,
						true);
				collisions.add(collision);
			}
		}
		return collisions;
	}

	/**
	 * Returns the level name
	 * 
	 * @return the level name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Adds a {@link LevelListener}
	 * 
	 * @param l
	 *            a level listener
	 */
	public void addLevelListener(LevelListener l) {
		listeners.add(LevelListener.class, l);
	}

	/**
	 * Removes a {@link LevelListener}
	 * 
	 * @param l
	 *            a level listener
	 */
	public void removeLevelListener(LevelListener l) {
		listeners.remove(LevelListener.class, l);
	}

	/**
	 * Resets the level and reloads all entities<br>
	 * <b>Annotation:</b> I know that this is not the safest way to create instances, <br>
	 * but the only way I could come up with that works with a level editor
	 */
	public void reset() {
		entities = new ArrayList<Entity>();
		killList = new ArrayList<Entity>();
		spawnList = new ArrayList<Entity>();
		listeners = new EventListenerList();
		List<Entity> entityList = new ArrayList<Entity>();
		Properties p;
		for (Class<? extends Entity> entityClass : entityMap.keySet()) {
			p = entityMap.get(entityClass);
			// This is were the magic happens
			try {
				Entity e = entityClass.newInstance();
				e.init(p);
				entityList.add(e);
			} catch (InstantiationException e1) {
				e1.printStackTrace();
			} catch (IllegalAccessException e1) {
				e1.printStackTrace();
			}

		}
		spawnManager = new DefaultSpawnManager(this, entityList, 800);
	}

}
